package physics
{
	import Box2D.Collision.Shapes.b2CircleShape;
	import Box2D.Collision.Shapes.b2PolygonShape;
	import Box2D.Collision.Shapes.b2Shape;
	import Box2D.Common.Math.b2Vec2;
	import Box2D.Dynamics.b2Body;
	import Box2D.Dynamics.b2BodyDef;
	import Box2D.Dynamics.b2ContactListener;
	import Box2D.Dynamics.b2DebugDraw;
	import Box2D.Dynamics.b2FixtureDef;
	import Box2D.Dynamics.b2World;
	import Box2D.Dynamics.Joints.b2Joint;
	import Box2D.Dynamics.Joints.b2PrismaticJoint;
	import Box2D.Dynamics.Joints.b2PrismaticJointDef;
	import Box2D.Dynamics.Joints.b2RevoluteJoint;
	import Box2D.Dynamics.Joints.b2RevoluteJointDef;
	import flash.display.DisplayObjectContainer;
	import flash.display.Sprite;
	import flash.display.Stage;
	import gameObjects.GameObject;
	import physics.contactListeners.ContactListener;
	/**
	 * @author Leong Shi Yun
	 */
	public class PhysicsManager
	{
		/* public const/var */
		public static const uiPHYSICS_FRAMERATE:uint = 30.0;
		
		/* private const/var */
		private var m_world:b2World;
		private var m_displayObjectContainer:DisplayObjectContainer;
		
		/* public functions */
		/**
		 * Inits the physics manager
		 * @param	displayObjContainer is the display object to add the b2 debug data on
		 * @param	applyGravity applies gravity if it's set true
		 * @param	showDebug draws the b2 debug data if ii is set to true
		 */
		public function PhysicsManager(displayObjContainer:DisplayObjectContainer, applyGravity:Boolean, drawDebug:Boolean)
		{
			if (applyGravity)
			{
				m_world = new b2World(new b2Vec2(0, CommonMath.nGRAVITY), true)
			}
			else
			{
				m_world = new b2World(new b2Vec2(0, 0), true);
			}
			
			if (drawDebug)
			{
				m_displayObjectContainer = displayObjContainer;
				addDebugData();
			}
		}
		
		/**
		 * Creates a circle shape
		 * @param	nRadius
		 * @return
		 */
		public function createCircleShape(nRadius:Number):b2Shape
		{
			var circleShape:b2CircleShape = new b2CircleShape(nRadius / CommonMath.uiMETER_TO_PIXEL_RATIO);
			return circleShape;
		}
		
		/**
		 * Creates an aligned box shape
		 * @param	nWidth
		 * @param	nHeight
		 * @return
		 */
		public function createAABBShape(nWidth:Number, nHeight:Number):b2Shape
		{
			var boxShape:b2PolygonShape = new b2PolygonShape();
			boxShape.SetAsBox(nWidth / CommonMath.uiMETER_TO_PIXEL_RATIO, nHeight / CommonMath.uiMETER_TO_PIXEL_RATIO);
			return boxShape;
		}
		
		/**
		 * Creates an oriented box shape
		 * @param	nWidth
		 * @param	nHeight
		 * @param	center
		 * @param	angle
		 * @return
		 */
		public function createOrientedBoxShape(nWidth:Number, nHeight:Number, center:b2Vec2, angle:Number):b2Shape
		{
			var boxShape:b2PolygonShape = new b2PolygonShape();
			center.x /= CommonMath.uiMETER_TO_PIXEL_RATIO;
			center.y /= CommonMath.uiMETER_TO_PIXEL_RATIO;
			boxShape.SetAsOrientedBox(nWidth / CommonMath.uiMETER_TO_PIXEL_RATIO, nHeight / CommonMath.uiMETER_TO_PIXEL_RATIO, center, angle);
			return boxShape;
		}
		
		/**
		 * Creates a polygon shape
		 * @param	vVertices
		 * @return
		 */
		public function createConvexPolyShape(vVertices:Vector.<b2Vec2>):b2Shape
		{
			var convexPoly:b2PolygonShape = new b2PolygonShape();
			
			//for each (x,y) in the vector, convert them from pixels to metres
			for each(var vtx:b2Vec2 in vVertices)
			{
				vtx.x /= CommonMath.uiMETER_TO_PIXEL_RATIO;
				vtx.y /= CommonMath.uiMETER_TO_PIXEL_RATIO;
			}
			
			convexPoly.SetAsVector(vVertices, vVertices.length);
			return convexPoly;
		}
		
		/**
		 * Creates a line shape
		 */
		public function createLineShape(v1:b2Vec2, v2:b2Vec2):b2Shape
		{
			var polygonShape:b2PolygonShape = new b2PolygonShape();
			
			v1.x /=  CommonMath.uiMETER_TO_PIXEL_RATIO;
			v1.y /=  CommonMath.uiMETER_TO_PIXEL_RATIO;
			
			v2.x /=  CommonMath.uiMETER_TO_PIXEL_RATIO;
			v2.y /=  CommonMath.uiMETER_TO_PIXEL_RATIO;
			
			polygonShape.SetAsEdge(v1, v2);
			return polygonShape;
		}
		 
		
		/**
		 * Creates a b2Fixture
		 * @param	shape
		 * @param	nDensity
		 * @param	nRestitution
		 * @param	nFriction
		 * @param	bIsSensor
		 * @param	collGroup
		 * @param	collWith
		 * @return
		 */
		public function createFixtureDef(shape:b2Shape, nDensity:Number, nRestitution:Number,
											nFriction:Number, bIsSensor:Boolean/*, collGroup:uint, collWith:uint*/):b2FixtureDef
		{
			var fixtureDef:b2FixtureDef = new b2FixtureDef();
			fixtureDef.shape = shape;
			fixtureDef.density = nDensity;
			fixtureDef.restitution = nRestitution;
			fixtureDef.friction = nFriction;
			fixtureDef.isSensor = bIsSensor;
			return fixtureDef;
		}
		
		/**
		 * Creates a b2Body
		 * @param	nPosX
		 * @param	nPosY
		 * @param	type
		 * @param	fixtureDef
		 * @return
		 */
		public function createBody(nPosX:Number, nPosY:Number, type:uint, fixtureDef:b2FixtureDef, bodyDef:b2BodyDef = null):b2Body
		{
			var body:b2Body = null;
			if (bodyDef == null)
			{
				bodyDef = new b2BodyDef();
				bodyDef.type = type;
				bodyDef.position.Set(nPosX / CommonMath.uiMETER_TO_PIXEL_RATIO,
										nPosY / CommonMath.uiMETER_TO_PIXEL_RATIO);
			}
			
			body = m_world.CreateBody(bodyDef);
			body.CreateFixture(fixtureDef);
			return body;
		}
		
		/**
		 * Creates a revolute joint between to b2 bodies
		 * @param	bConvertToMeter
		 * @param	b2Body1
		 * @param	b2Body2
		 * @param	anchor
		 * @param	nUpperAngle
		 * @param	nLowerAngle
		 * @param	bEnableLimit
		 * @param	nMaxMotorTorque
		 * @param	nMotorSpeed
		 * @param	bEnableMotor
		 * @return
		 */
		public function createRevoluteJoint(bConvertToMeter:Boolean,
											b2Body1:b2Body,
											b2Body2:b2Body,
											anchor:b2Vec2,
											bEnableLimit:Boolean = false,
											nUpperAngle:Number = 1.0,
											nLowerAngle:Number = -1.0,
											bEnableMotor:Boolean = false,
											nMaxMotorTorque:Number =0.0,
											nMotorSpeed:Number = 0.0):b2RevoluteJoint
		{
			var jointRevDef:b2RevoluteJointDef = new b2RevoluteJointDef();
			if (bConvertToMeter)
			{
				anchor.x /= CommonMath.uiMETER_TO_PIXEL_RATIO;
				anchor.y /= CommonMath.uiMETER_TO_PIXEL_RATIO;
			}
			
			jointRevDef.Initialize(b2Body1, b2Body2, anchor);
			
			jointRevDef.enableLimit = bEnableLimit;
			if (jointRevDef.enableLimit)
			{
				jointRevDef.upperAngle = CommonMath.degreeToRadian(nUpperAngle);
				jointRevDef.lowerAngle = CommonMath.degreeToRadian(nLowerAngle);
			}
			
			jointRevDef.enableMotor = bEnableMotor;
			if (jointRevDef.enableMotor)
			{
				jointRevDef.maxMotorTorque = nMaxMotorTorque;
				jointRevDef.motorSpeed = nMotorSpeed;
			}
			
			return m_world.CreateJoint(jointRevDef) as b2RevoluteJoint;
		}
		
		public function createPrismaticJoint(bConvertToMeter:Boolean,
												body1:b2Body,
												body2:b2Body,
												anchor:b2Vec2,
												axis:b2Vec2,
												bEnableLimit:Boolean = false,
												nUpperTranslation:Number = 0,
												nLowerTranslation:Number = 0,
												bEnableMotor:Boolean = false,
												nMaxMotorForce:Number = 0,
												nMotorSpeed:Number = 0):b2PrismaticJoint
		{
			var jointPrisDef:b2PrismaticJointDef = new b2PrismaticJointDef();
			if (bConvertToMeter)
			{
				anchor.x /= CommonMath.uiMETER_TO_PIXEL_RATIO;
				anchor.y /= CommonMath.uiMETER_TO_PIXEL_RATIO;
			}
			
			jointPrisDef.Initialize(body1, body2, anchor, axis);
			
			jointPrisDef.enableLimit = bEnableLimit;
			if (jointPrisDef.enableLimit)
			{
				jointPrisDef.upperTranslation = nUpperTranslation;
				jointPrisDef.lowerTranslation = nLowerTranslation;
			}
			
			jointPrisDef.enableMotor = bEnableMotor;
			if (jointPrisDef.enableMotor)
			{
				jointPrisDef.maxMotorForce = nMaxMotorForce;
				jointPrisDef.motorSpeed = nMotorSpeed;
			}
			
			return m_world.CreateJoint(jointPrisDef) as b2PrismaticJoint;
		}
		
		/**
		 * Sets a contact listener to the world
		 * @param	contactListener
		 */
		public function setContactListener(contactListener:ContactListener):void
		{
			m_world.SetContactListener(contactListener);
		}
		
		/**
		 * Updates all physics manger behaviours
		 */
		public function update():void
		{
			if (m_world)
			{
				m_world.Step(1.0 / uiPHYSICS_FRAMERATE, 10, 10);
				//m_world.ClearForces();
				
				CONFIG::debug
				{
					m_world.DrawDebugData();
				}
			}
		}
		
		/**
		 * Destroys a b2Body in the world
		 * @param	body:b2Body
		 */
		public function destroyBody(body:b2Body):void
		{
			if (body)
			{
				m_world.DestroyBody(body);
				body = null;
				m_world.DrawDebugData();
				
				var bodyWorld:b2Body = null;
				if (m_world.GetBodyCount() < 2)
				{
					bodyWorld = m_world.GetBodyList();
					if (bodyWorld)
					{
						bodyWorld.SetPosition(new b2Vec2(50 / CommonMath.uiMETER_TO_PIXEL_RATIO, 300 /  CommonMath.uiMETER_TO_PIXEL_RATIO));
						m_world.DestroyBody(bodyWorld);
					}
				}
			}
		}
		
		/**
		 * Destroys a b2Joint in the world
		 * @param	joint:b2Joint
		 */
		public function destroyJoint(joint:b2Joint):void
		{
			if (joint)
			{
				m_world.DestroyJoint(joint);
				joint = null;
				m_world.DrawDebugData();
				
				var jointWorld:b2Joint = null;
				if (m_world.GetJointCount() < 2)
				{
					jointWorld = m_world.GetJointList();
					if (jointWorld)
					{
						m_world.DestroyJoint(jointWorld);
					}
				}
			}
		}
		
		/**
		 * Destroys the physics manager
		 */
		public function destroy():void
		{
			m_world.ClearForces();
			m_world = null;
		}
		
		/* private functions */
		/**
		 * Draws b2 debug data
		 */
		public function addDebugData():void
		{
			var dbgDraw:b2DebugDraw = new b2DebugDraw();
			var sprite:Sprite = new Sprite();
			m_displayObjectContainer.addChild(sprite);
			dbgDraw.SetSprite(sprite);
			dbgDraw.SetDrawScale(CommonMath.uiMETER_TO_PIXEL_RATIO);
			dbgDraw.SetFillAlpha(0.5);
			dbgDraw.SetLineThickness(1);
			dbgDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit | b2DebugDraw.e_centerOfMassBit);
			m_world.SetDebugDraw(dbgDraw);
		}
		
		public function get worldB2():b2World
		{
			return m_world;
		}
		
	}

}
